﻿#region Header
// Copyright 2007-2011, Jerónimo Milea
// 
// This file is part of NetJsWire.
// 
//     NetJsWire is free software: you can redistribute it and/or modify
//     it under the terms of the GNU General Public License as published by
//     the Free Software Foundation, either version 3 of the License, or
//     (at your option) any later version.
// 
//     NetJsWire is distributed in the hope that it will be useful,
//     but WITHOUT ANY WARRANTY; without even the implied warranty of
//     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//     GNU General Public License for more details.
// 
//     You should have received a copy of the GNU General Public License
//     along with NetJsWire.  If not, see <http://www.gnu.org/licenses/>.
#endregion

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.Drawing;
using System.Drawing.Design;
using System.Dynamic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Text;
using System.Windows.Forms;
using NetJsWire.Lite.Attributes;
using NetJsWire.Lite.Converters;
using NetJsWire.Lite.Designers;
using NetJsWire.Lite.Editors;
using NetJsWire.Lite.Helpers;
using NetJsWire.Lite.Interfaces;
using NetJsWire.Lite.Properties;
using NetJsWire.Lite.Resolver;
using Newtonsoft.Json;

namespace NetJsWire.Lite.Widgets.UI
{
    [Designer( typeof( BaseComponentDesigner ) )]
    [JsonObject( MemberSerialization = MemberSerialization.OptIn )]
    public abstract class BaseComponent : ScrollableControl, IBaseComponent, IObservable, ISerializer, IDynamicMetaObjectProvider, ICustomTypeDescriptor
    {
        protected static Color DefaultBackgroundColor = Color.FromArgb( 206, 217, 231 );
        private readonly ObservableCollection<Property> _extraProperties;
        internal Color BackgroundColor = Color.FromArgb( 206, 217, 231 );
        protected Formatting SerializationFormating = Formatting.None;
        private string _fieldLabel;
        private float _flex;
        private string _labelSeparator;

        protected BaseComponent()
            : this( null )
        {
        }

        protected BaseComponent( IContainer container )
        {
            if ( container != null )
                container.Add( this );
            base.DoubleBuffered = true;
            SetStyle( ControlStyles.OptimizedDoubleBuffer |
                                    ControlStyles.UserPaint |
                                    ControlStyles.AllPaintingInWmPaint, true );

            Listeners = new ListenersCollection();

            _extraProperties = new ObservableCollection<Property>();

            if ( Settings.Default != null )
            {
                DefaultBackgroundColor = Settings.Default.BaseComponentBackgroundColor;
            }

            base.BackColor = DefaultBackgroundColor;
            BackgroundColor = DefaultBackgroundColor;
        }

        public override Rectangle DisplayRectangle
        {
            get
            {
                return new Rectangle( 0, 0, ClientRectangle.Width, ClientRectangle.Height );
            }
        }

        [Browsable( false )]
        [JsonIgnore]
        public new BoxComponent Parent
        {
            get { return base.Parent as BoxComponent; }
            set { base.Parent = value; }
        }

        [DefaultValue( ( string ) null )]
        [JsonProperty( "fieldLabel", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore )]
        [Description( "The label text to display next to this Component.\nNote: this config is only used when this Component is rendered by a Container which has been configured to use the FormLayout layout manager." )]
        public virtual string FieldLabel
        {
            get { return _fieldLabel; }
            set
            {
                _fieldLabel = value;
                if ( Parent == null ) return;
                Parent.PerformLayout();
                Parent.Invalidate();
            }
        }

        [Browsable( true )]
        [JsonProperty( "hidden", DefaultValueHandling = DefaultValueHandling.Ignore )]
        [DefaultValue( false )]
        [Description( "Render this component hidden (default is false). If true, the hide method will be called internally." )]
        public virtual bool Hidden { get; set; }

        [JsonProperty( "data", NullValueHandling = NullValueHandling.Ignore, DefaultValueHandling = DefaultValueHandling.Ignore )]
        [Description( "The initial set of data to apply to the tpl to update the content area of the Component." )]
        [TypeConverter( typeof( StringConverter ) )]
        [Editor( typeof( ScriptEditor ), typeof( UITypeEditor ) )]
        [JsonConverter( typeof( NotQuotedJsonStringConverter ) )]
        public object Data { get; set; }

        [DefaultValue( ( string ) null )]
        [JsonProperty( "labelSeparator", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore )]
        [Description( "The separator to display after the text of each fieldLabel." )]
        public virtual string LabelSeparator
        {
            get { return _labelSeparator; }
            set
            {
                _labelSeparator = value;

                if ( !( this is Container ) )
                {
                    if ( Parent != null )
                        Parent.Invalidate( true );
                }
                else
                    Invalidate( true );
            }
        }

        [JsonProperty( "id", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore )]
        [DefaultValue( ( string ) null )]
        [Description( "The unique id of this component (defaults to an auto-assigned id). You should assign an id if you need to be able to access the component later and you do not have an object reference available (e.g., using Ext.getCmp).\nNote that this id will also be used as the element id for the containing HTML element that is rendered to the page for this component. This allows you to write id-based CSS rules to style the specific instance of this component uniquely, and also to select sub-elements using this component's id as the parent." )]
        public virtual string Id { get; set; }

        [JsonProperty( "itemId", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore )]
        [DefaultValue( ( string ) null )]
        [Description( "An itemId can be used as an alternative way to get a reference to a component when no object reference is available. Instead of using an id with Ext.getCmp, use itemId with Ext.Container.getComponent which will retrieve itemId's or id's. Since itemId's are an index to the container's internal MixedCollection, the itemId is scoped locally to the container -- avoiding potential conflicts with Ext.ComponentMgr which requires a unique id." )]
        public virtual string ItemId { get; set; }

        [JsonProperty( "ref", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore )]
        [DefaultValue( ( string ) null )]
        [Description( "A path specification, relative to the Component's ownerCt specifying into which ancestor Container to place a named reference to this Component.\nThe ancestor axis can be traversed by using '/' characters in the path." )]
        public virtual string Ref { get; set; }

        [DefaultValue( false )]
        [JsonProperty( "disabled", DefaultValueHandling = DefaultValueHandling.Ignore )]
        [Description( "True to start disabled." )]
        public virtual bool Disabled { get; set; }

        [Browsable( true )]
        [JsonProperty( "renderTo", DefaultValueHandling = DefaultValueHandling.Ignore )]
        [DefaultValue( ( string ) null )]
        [Description( "Specify the id of the element, a DOM element or an existing Element that this component will be rendered into." )]
        public string RenderTo { get; set; }

        [JsonProperty( "style", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore )]
        [DefaultValue( ( string ) null )]
        [Description( "A custom style specification to be applied to this component's Element. Should be a valid argument to Ext.Element.applyStyles." )]
        [JsonConverter( typeof( NotQuotedJsonStringConverter ) )]
        public string Style { get; set; }

        [JsonProperty( "tpl", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore )]
        [DefaultValue( ( string ) null )]
        [Description( "The template string, or Ext.XTemplate instance to use to display each item in the dropdown list. The dropdown list is displayed in a DataView. " )]
        [JsonConverter( typeof( NotQuotedJsonStringConverter ) )]
        [Editor( typeof( ScriptEditor ), typeof( UITypeEditor ) )]
        public string Tpl { get; set; }

        /// <summary>
        ///   Note: this config is only used when this Component is rendered by a Container which has been configured to use a BoxLayout. Each child Component with a flex property will be flexed either vertically (by a VBoxLayout) or horizontally (by an HBoxLayout) according to the item's relative flex value compared to the sum of all Components with flex value specified. Any child items that have either a flex = 0 or flex = undefined will not be 'flexed' (the initial size will not be changed).
        /// </summary>
        [JsonProperty( "flex", DefaultValueHandling = DefaultValueHandling.Ignore )]
        [DefaultValue( 0f )]
        [Description( "Note: this config is only used when this Component is rendered by a Container which has been configured to use a BoxLayout." )]
        public virtual float Flex
        {
            get { return _flex; }
            set
            {
                if ( value < 0 )
                    return;
                _flex = value;
                if ( Parent != null )
                {
                    Parent.PerformLayout();
                    Parent.Invalidate( true );
                }
            }
        }

        [DesignerSerializationVisibility( DesignerSerializationVisibility.Content )]
        [RefreshProperties( RefreshProperties.All )]
        public ObservableCollection<Property> ExtraProperties
        {
            get { return _extraProperties; }
        }

        [Browsable( true )]
        [JsonProperty( "plugins", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore )]
        [JsonConverter( typeof( NotQuotedJsonStringConverter ) )]
        [DefaultValue( null )]
        [Description( "An object or array of objects that will provide custom functionality for this component." )]
        public string Plugins { get; set; }

        public bool ShouldSerializeExtraProperties()
        {
            return ExtraProperties.Count > 0;
        }

        public void ResetExtraProperties()
        {
            ExtraProperties.Clear();
        }

        protected Window FindWindow()
        {
            return GetParentWindow( this ) as Window;
        }

        private static BaseComponent GetParentWindow( BaseComponent component )
        {
            if ( component is Window )
                return component;

            if ( component.Parent != null )
            {
                if ( component.Parent is Window )
                {
                    return component.Parent;
                }
                return GetParentWindow( component.Parent );
            }

            return null;
        }

        public bool ShouldSerializeData()
        {
            return Data != null && !string.IsNullOrWhiteSpace( Data.ToString() );
        }

        protected void ResetData()
        {
            Data = null;
        }

        [OnSerializing]
        internal void OnSerializingMethod( StreamingContext context )
        {
        }

        #region IBaseComponent Members

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Advanced )]
        [JsonIgnore]
        public Container ContainerControl { get; set; }

        #endregion

        #region Implementation of IComponent

        [NonSerialized]
        private ISite _site;

        /// <summary>
        ///   Obtiene o establece la interfaz <see cref = "T:System.ComponentModel.ISite" /> asociada a la interfaz <see cref = "T:System.ComponentModel.IComponent" />.
        /// </summary>
        /// <returns>
        ///   Objeto <see cref = "T:System.ComponentModel.ISite" /> asociado al componente; o null, si el componente no tiene un sitio asociado.
        /// </returns>
        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Advanced )]
        [JsonIgnore]
        public override ISite Site
        {
            get { return _site; }
            set
            {
                _site = value;
                if ( value == null )
                    return;

                var host = ( IDesignerHost ) value.GetService( typeof( IDesignerHost ) );

                if ( host == null )
                    return;

                var baseComp = host.RootComponent;

                if ( baseComp is Container )
                    ContainerControl = ( Container ) baseComp;
            }
        }

        #endregion

        #region Implementation of IObservable

        /// <summary>
        ///   Note: this config is only used when this Component is rendered by a Container which has been configured to use the FormLayout layout manager (e.g. Ext.form.FormPanel or specifying layout:'form').
        ///   An additional CSS class to apply to the div wrapping the form item element of this field. If supplied, itemCls at the field level will override the default itemCls supplied at the container level. The value specified for itemCls will be added to the default class ('x-form-item').
        ///   Since it is applied to the item wrapper (see Ext.layout.FormLayout.fieldTpl), it allows you to write standard CSS rules that can apply to the field, the label (if specified), or any other element within the markup for the field.
        ///   <example>
        ///     <code>
        ///       // Apply a style to the field's label:
        ///       <style>
        ///         .required .x-form-item-label {font-weight:bold;color:red;}
        ///       </style>
        /// 
        ///       new Ext.FormPanel({
        ///       height: 100,
        ///       renderTo: Ext.getBody(),
        ///       items: [{
        ///       xtype: 'textfield',
        ///       fieldLabel: 'Name',
        ///       itemCls: 'required' //this label will be styled
        ///       },{
        ///       xtype: 'textfield',
        ///       fieldLabel: 'Favorite Color'
        ///       }]
        ///       });
        ///     </code>
        ///   </example>
        /// </summary>
        [JsonProperty( "itemCls", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore )]
        [DefaultValue( ( string ) null )]
        [Description( "This config is only used when this Component is rendered by a Container which has been configured to use the FormLayout layout manager." )]
        public virtual string ItemCls { get; set; }

        /// <summary>
        ///   A config object containing one or more event handlers to be added to this object during initialization.<br />
        ///   While some ExtJs Component classes export selected DOM events (e.g. "click", "mouseover" etc), this is usually only done when extra value can be added. 
        ///   For example the DataView's click event passing the node clicked on. 
        ///   To access DOM events directly from a Component's HTMLElement, listeners must be added to the Element after the Component has been rendered.
        /// </summary>
        [Description( "A config object containing one or more event handlers to be added to this object during initialization." )]
        [DesignerSerializationVisibility( DesignerSerializationVisibility.Content )]
        [JsonProperty( "listeners", DefaultValueHandling = DefaultValueHandling.Ignore, NullValueHandling = NullValueHandling.Ignore, Required = Required.AllowNull )]
        [Browsable( true )]
        [ListenerCollection( true )]
        public ListenersCollection Listeners { get; set; }

        public bool ShouldSerializeListeners()
        {
            return Listeners.Count > 0;
        }

        protected void ResetListeners()
        {
            Listeners.Clear();
        }

        #endregion

        #region Implementation of ISerializer

        public virtual string Serialize()
        {
            var sb = new StringBuilder();
            var serializer = JsonSerializer.Create( new JsonSerializerSettings
                                                        {
                                                            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
                                                            DefaultValueHandling = DefaultValueHandling.Ignore,
                                                            NullValueHandling = NullValueHandling.Ignore,
                                                            Converters =
                                                                new JsonConverter[]
                                                                    {
                                                                        //new BaseComponentConverter(),
                                                                        new ListenerCollectionJsonConverter(),
                                                                        new ColumnModelJsonConverter(),
                                                                        new GridViewJsonConverter(),
                                                                        new DataProxyJsonConverter(),
                                                                        new DataReaderJsonConverter(),
                                                                        new DataWriterJsonConverter(),
                                                                        new CheckBoxSelectionModelColumnJsonConverter()
                                                                    }
                                                                    ,
                                                            ContractResolver = new ComponentJsonResolver()
                                                        } );

            var writer = new JsonTextWriter( new StringWriter( sb ) ) { QuoteName = false, Formatting = SerializationFormating };
            serializer.Serialize( writer, this );
            var result = sb.ToString();

            return result.Replace( "___store", "store" ).Replace( "_items_", "items" );
        }

        public string Serialize( Formatting formatting )
        {
            SerializationFormating = formatting;
            return Serialize();
        }

        protected bool _serializingAsClass;
        public string SerializeClass()
        {
            _serializingAsClass = true;
            var initComponentAdded = false;
            try
            {
                if ( !ExtraProperties.Any( p => p.Name == "initComponent" ) )
                {
                    initComponentAdded = true;
                    ExtraProperties.Add( new Property { Name = "initComponent", Value = string.Format( "function(){{ {0}.superclass.initComponent.call(this);}}", Name ) } );
                }
                var serialized = Serialize();
                if(serialized.EndsWith( ";" ))
                {
                    serialized = serialized.Substring( 0, serialized.Length - 1 );
                }
                return string.Format( "{0} = Ext.extend( {1}, {2} );", Name, GetClassName(), serialized );
            }
            finally
            {
                _serializingAsClass = false;
                if ( initComponentAdded )
                {
                    ExtraProperties.Remove( ExtraProperties.First( p => p.Name == "initComponent" ) );
                }
            }
        }

        public virtual string SerializeClass( Formatting formatting )
        {
            SerializationFormating = formatting;
            return SerializeClass();
        }

        #endregion

        #region Drawing design

        protected override void WndProc( ref Message m )
        {
            switch ( ( Native.Msg ) m.Msg )
            {
                case Native.Msg.WM_NCCALCSIZE:
                    WmNCCalcSize( ref m );
                    break;
                case Native.Msg.WM_SETREDRAW:
                    InvalidateWindow();
                    break;
                case Native.Msg.WM_NCPAINT:
                    DrawNcArea();
                    break;
                default:
                    base.WndProc( ref m );
                    break;
            }
        }

        protected virtual void WmNCCalcSize( ref Message m )
        {
            // http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/windows/windowreference/windowmessages/wm_nccalcsize.asp
            // http://groups.google.pl/groups?selm=OnRNaGfDEHA.1600%40tk2msftngp13.phx.gbl

            if ( m.WParam == NativeMethods.FALSE )
            {
                var ncRect = ( NativeMethods.RECT ) m.GetLParam( typeof( NativeMethods.RECT ) );
                var proposed = ncRect.Rect;

                OnNonClientAreaCalcSize( ref proposed, true );
                ncRect = NativeMethods.RECT.FromRectangle( proposed );

                Marshal.StructureToPtr( ncRect, m.LParam, false );
            }
            else if ( m.WParam == NativeMethods.TRUE )
            {
                var ncParams =
                    ( NativeMethods.NCCALCSIZE_PARAMS ) m.GetLParam( typeof( NativeMethods.NCCALCSIZE_PARAMS ) );
                var proposed = ncParams.rectProposed.Rect;

                OnNonClientAreaCalcSize( ref proposed, true );
                ncParams.rectProposed = NativeMethods.RECT.FromRectangle( proposed );

                Marshal.StructureToPtr( ncParams, m.LParam, false );
            }
            m.Result = IntPtr.Zero;
        }

        protected virtual void OnNonClientAreaCalcSize( ref Rectangle bounds, bool update )
        {
            var clientPadding = new Padding( 0 );
            bounds = new Rectangle( bounds.Left + clientPadding.Left, bounds.Top + clientPadding.Top,
                                    bounds.Width - clientPadding.Horizontal, bounds.Height - clientPadding.Vertical );
        }


        protected virtual void DrawNcArea()
        {
            if ( !IsHandleCreated )
                return;

            var hDc = Native.GetWindowDC( Handle );
            if ( hDc == IntPtr.Zero )
                return;
            var winRect = new Native.RECT();
            if ( !Native.GetWindowRect( Handle, ref winRect ) )
                return;
            //var g = Graphics.FromHdc( hDc );
            var bounds = new Rectangle( 0, 0, Bounds.Width, Bounds.Height );
            var compatiblehDc = NativeMethods.CreateCompatibleDC( hDc );
            if ( compatiblehDc == IntPtr.Zero )
            {
                return;
            }
            var compatibleBitmap = NativeMethods.CreateCompatibleBitmap( hDc, bounds.Width, bounds.Height );
            if ( compatibleBitmap == IntPtr.Zero )
            {
                return;
            }

            try
            {
                NativeMethods.SelectObject( compatiblehDc, compatibleBitmap );

                // copy current screen to bitmap
                //TODO: this is quite slow (80% of this method). Why?
                NativeMethods.BitBlt( compatiblehDc, 0, 0, bounds.Width, bounds.Height, hDc, 0, 0,
                                      NativeMethods.TernaryRasterOperations.SRCCOPY );

                using ( var g = Graphics.FromHdc( compatiblehDc ) )
                {
                    OnPaintNcArea( new PaintEventArgs( g, bounds ) );
                }

                // copy current from bitmap to screen
                NativeMethods.BitBlt( hDc, 0, 0, bounds.Width, bounds.Height, compatiblehDc, 0, 0,
                                      NativeMethods.TernaryRasterOperations.SRCCOPY );
            }
            finally
            {
                NativeMethods.DeleteObject( compatibleBitmap );
                NativeMethods.DeleteDC( compatiblehDc );
            }
        }

        internal virtual void OnPaintInternal( PaintEventArgs e )
        {
            OnPaint( e );
        }

        protected virtual void OnPaintNcArea( PaintEventArgs e )
        {
            //DrawNcArea();
        }

        protected override void OnPaintBackground( PaintEventArgs pevent )
        {
            //if ( this is Window )
            //{
            //    MessageBox.Show( "Llega Window, " + this.BackColor );
            //}
            //base.OnPaintBackground( pevent );
            using ( var br = new SolidBrush( _backColor ) )
            {
                pevent.Graphics.FillRectangle( br, pevent.ClipRectangle );
            }
        }

        /// <summary>
        ///   This method should invalidate entire window including the non-client area.
        /// </summary>
        internal void InvalidateWindow()
        {
            if ( !IsDisposed && IsHandleCreated )
            {
                NativeMethods.SetWindowPos( Handle, IntPtr.Zero, 0, 0, 0, 0,
                                            ( int )
                                            ( NativeMethods.SetWindowPosOptions.SWP_NOACTIVATE |
                                              NativeMethods.SetWindowPosOptions.SWP_NOMOVE |
                                              NativeMethods.SetWindowPosOptions.SWP_NOSIZE |
                                              NativeMethods.SetWindowPosOptions.SWP_NOZORDER |
                                              NativeMethods.SetWindowPosOptions.SWP_NOOWNERZORDER |
                                              NativeMethods.SetWindowPosOptions.SWP_FRAMECHANGED ) );

                NativeMethods.RedrawWindow( Handle, IntPtr.Zero, IntPtr.Zero,
                                            ( int )
                                            ( NativeMethods.RedrawWindowOptions.RDW_FRAME |
                                              NativeMethods.RedrawWindowOptions.RDW_UPDATENOW |
                                              NativeMethods.RedrawWindowOptions.RDW_INVALIDATE ) );
            }
        }

        #endregion

        #region Hidden properties from base class

        private Color _backColor;

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( null )]
        [JsonIgnore]
        public new string AccessibleDescription { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( null )]
        [JsonIgnore]
        public new string AccessibleName { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( null )]
        [JsonIgnore]
        public new string AccessibleRole { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( false )]
        [JsonIgnore]
        public override bool AllowDrop { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( false )]
        [JsonIgnore]
        public override bool AutoScroll
        {
            get { return base.AutoScroll; }
            set { base.AutoScroll = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( typeof( Size ), "0, 0" )]
        [JsonIgnore]
        public new Size AutoScrollMargin
        {
            get { return base.AutoScrollMargin; }
            set { base.AutoScrollMargin = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( typeof( Size ), "0, 0" )]
        [JsonIgnore]
        public new Size AutoScrollMinSize
        {
            get { return base.AutoScrollMinSize; }
            set { base.AutoScrollMinSize = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( null )]
        [JsonIgnore]
        public override ContextMenu ContextMenu { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( AnchorStyles.None )]
        [JsonIgnore]
        public override AnchorStyles Anchor { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never )]
        [JsonIgnore]
        public override Color BackColor
        {
            get { return _backColor; }
            set { _backColor = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( null )]
        [JsonIgnore]
        public override Image BackgroundImage { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( ImageLayout.None )]
        [JsonIgnore]
        public override ImageLayout BackgroundImageLayout { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( null )]
        [JsonIgnore]
        public override ContextMenuStrip ContextMenuStrip { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( false )]
        [JsonIgnore]
        public new bool CausesValidation { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( null )]
        [JsonIgnore]
        public override Cursor Cursor { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( DockStyle.None )]
        [JsonIgnore]
        public override DockStyle Dock { get { return base.Dock; } set { base.Dock = value; } }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never )]
        [JsonIgnore]
        public override Font Font
        {
            get { return base.Font; }
            set { base.Font = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never )]
        [JsonIgnore]
        public override Color ForeColor
        {
            get { return base.ForeColor; }
            set { base.ForeColor = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( ImeMode.NoControl )]
        [JsonIgnore]
        public new ImeMode ImeMode { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Advanced )]
        [JsonIgnore]
        public new Padding Margin
        {
            get { return base.Margin; }
            set { base.Margin = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never )]
        [JsonIgnore]
        public override Size MaximumSize
        {
            get { return base.MaximumSize; }
            set { base.MaximumSize = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never )]
        [JsonIgnore]
        public override Size MinimumSize
        {
            get { return base.MinimumSize; }
            set { base.MinimumSize = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Advanced )]
        [JsonIgnore]
        public new Padding Padding
        {
            get { return base.Padding; }
            set { base.Padding = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( RightToLeft.No )]
        [JsonIgnore]
        public override RightToLeft RightToLeft
        {
            get { return base.RightToLeft; }
            set { base.RightToLeft = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( false )]
        [JsonIgnore]
        public new bool TabStop { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( ( string ) null )]
        public override string Text
        {
            get { return base.Text; }
            set { base.Text = value; }
        }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( false )]
        [JsonIgnore]
        public new bool UseWaitCursor { get; set; }

        [Browsable( false ), EditorBrowsable( EditorBrowsableState.Never ), DefaultValue( false )]
        [JsonIgnore]
        public new bool Enabled { get; set; }

        public bool ShouldSerializeBackColor()
        {
            return false;
        }

        #endregion

        #region Implementation of IDynamicMetaObjectProvider

        public virtual DynamicMetaObject GetMetaObject( Expression parameter )
        {
            return new DynamicProxyMetaObject<BaseComponent>( parameter, this, new BaseComponentDynamicProxy(), true );
        }


        private class BaseComponentDynamicProxy : DynamicProxy<BaseComponent>
        {
            public override bool TryGetMember( BaseComponent instance, GetMemberBinder binder, out object result )
            {
                result = null;
                var prop = instance.ExtraProperties.FirstOrDefault( p => p.Name == binder.Name );
                if ( prop != null )
                {
                    result = new Property.PropertyValue( prop.Value );
                    return true;
                }

                return false;
            }

            public override bool TrySetMember( BaseComponent instance, SetMemberBinder binder, object value )
            {
                var prop = instance.ExtraProperties.FirstOrDefault( p => p.Name == binder.Name );
                if ( prop != null )
                {
                    prop.Value = value;
                    return true;
                }

                instance.ExtraProperties.Add( new Property { Name = binder.Name, Value = value } );
                return true;
            }

            public override IEnumerable<string> GetDynamicMemberNames( BaseComponent instance )
            {
                return new List<string>( instance.ExtraProperties.Select( p => p.Name ) );
            }
        }

        #endregion

        #region Implementation of ICustomTypeDescriptor

        public AttributeCollection GetAttributes()
        {
            return TypeDescriptor.GetAttributes( this, true );
        }

        public string GetClassName()
        {
            return TypeDescriptor.GetClassName( this, true );
        }

        public string GetComponentName()
        {
            return TypeDescriptor.GetComponentName( this, true );
        }

        public TypeConverter GetConverter()
        {
            return TypeDescriptor.GetConverter( this, true );
        }

        public EventDescriptor GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent( this, true );
        }

        public PropertyDescriptor GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty( this, true );
        }

        public object GetEditor( Type editorBaseType )
        {
            return TypeDescriptor.GetEditor( this, editorBaseType, true );
        }

        public EventDescriptorCollection GetEvents()
        {
            return TypeDescriptor.GetEvents( this, true );
        }

        public EventDescriptorCollection GetEvents( Attribute[] attributes )
        {
            return TypeDescriptor.GetEvents( this, attributes, true );
        }

        public PropertyDescriptorCollection GetProperties()
        {
            return GetProperties( new Attribute[ 0 ] );
        }

        public PropertyDescriptorCollection GetProperties( Attribute[] attributes )
        {
            var originalProperties = TypeDescriptor.GetProperties( this, attributes, true );
            var newProperties = new List<PropertyDescriptor>();

            if ( ShouldSerializeExtraProperties() )
            {
                newProperties.AddRange( originalProperties.Cast<PropertyDescriptor>() );
                foreach ( var property in ExtraProperties )
                {
                    if ( !string.IsNullOrWhiteSpace( property.Name ) )
                    {
                        var jsonProp = new JsonPropertyAttribute( property.Name );
                        jsonProp.DefaultValueHandling = DefaultValueHandling.Include;
                        jsonProp.NullValueHandling = NullValueHandling.Include;

                        var attrs = new Attribute[]
                                        {
                                            jsonProp, BrowsableAttribute.Yes,
                                            new RefreshPropertiesAttribute( RefreshProperties.All ),
                                            new CategoryAttribute( "Dynamic" ),
                                            new DesignerSerializationVisibilityAttribute( DesignerSerializationVisibility.Hidden ),
                                            new NotifyParentPropertyAttribute( true ),
                                            new ParenthesizePropertyNameAttribute( true ),
                                            new DescriptionAttribute( "(dynamic) property" )
                                        };
                        var ppd = new PropertyTypeDescriptor( property.Name, attrs, this );
                        newProperties.Add( ppd );
                    }
                }

                return new PropertyDescriptorCollection( newProperties.ToArray(), true ); ;
            }

            return originalProperties;
        }

        public object GetPropertyOwner( PropertyDescriptor pd )
        {
            return this;
        }

        #endregion
    }

    public class Property
    {
        private const string StringName = "Property";

        [DefaultValue( ( string ) null )]
        public string Name { get; set; }

        [DefaultValue( ( string ) null )]
        [Editor( typeof( ScriptEditor ), typeof( UITypeEditor ) )]
        public object Value { get; set; }


        public override string ToString()
        {
            return StringName;
        }

        [JsonConverter( typeof( NotQuotedJsonStringConverter ) )]
        public class PropertyValue
        {
            private object _value;

            public PropertyValue( object value )
            {
                _value = value;
            }

            public object Value { get { return _value; } set { _value = value; } }

            public override string ToString()
            {
                return _value.ToString();
            }
        }
    }

    internal class PropertyTypeDescriptor : PropertyDescriptor
    {
        private Type _componentType = typeof( Property );

        private bool _isReadOnly = false;

        private Type _propertyType = typeof( string );

        //The name of the property
        private string _name;
        //The owner component
        private BaseComponent _component;
        private Property _prop;

        public PropertyTypeDescriptor( string name, Attribute[] attrs, BaseComponent parent )
            : this( name, attrs )
        {
            _component = parent;
            _prop = _component.ExtraProperties.FirstOrDefault( p => p.Name == name );
        }

        public PropertyTypeDescriptor( string name, Attribute[] attrs )
            : base( name, attrs )
        {
            _name = name;
        }

        public PropertyTypeDescriptor( MemberDescriptor descr )
            : base( descr )
        {
        }

        public PropertyTypeDescriptor( MemberDescriptor descr, Attribute[] attrs )
            : base( descr, attrs )
        {
        }

        #region Overrides of PropertyDescriptor

        public override bool CanResetValue( object component )
        {
            return false;
        }

        public override object GetValue( object component )
        {
            return _prop.Value;
        }

        public override void ResetValue( object component )
        {
            _prop.Value = null;
        }

        public override void SetValue( object component, object value )
        {
            _prop.Value = value;
        }

        public override bool ShouldSerializeValue( object component )
        {
            return false;
        }

        public override Type ComponentType
        {
            get { return _componentType; }
        }

        public override bool IsReadOnly
        {
            get { return _isReadOnly; }
        }

        public override Type PropertyType
        {
            get { return _propertyType; }
        }

        #endregion
    }
}